package com.hts.utils.word;

import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.deepoove.poi.xwpf.NiceXWPFDocument;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author hs
 * @version 1.0
 * @date: 2022/12/8
 */
@Slf4j
public class WordUtils {
    private final static String regxNumber = "^-?\\d+";
    private final static Pattern pattern = Pattern.compile(regxNumber);

    public static void main(String[] args) throws Exception {
//        testMerge();
//        FileInputStream fileInputStream = new FileInputStream("F:\\word\\1.docx");
//        FileInputStream fileInputStream1 = new FileInputStream("F:\\word\\1-1.docx");
//        FileInputStream fileInputStream2 = new FileInputStream("F:\\word\\1-1-1.docx");
//        ArrayList<InputStream> objects = new ArrayList<>();
//        objects.add(fileInputStream);
//        objects.add(fileInputStream1);
//        objects.add(fileInputStream2);
////        objects.add(fileInputStream);
//
//        ByteArrayOutputStream byteArrayOutputStream = mergeDocAndPageByInputStream(objects);
//        FileOutputStream fileOutputStream = new FileOutputStream("F:\\word\\merge.docx");
//        fileOutputStream.write(byteArrayOutputStream.toByteArray());
        String path = "F:\\word\\merge.docx";
        ByteArrayOutputStream byteArrayOutputStream = interceptByInputStream(new FileInputStream(path), "文本库");
        File new_file = new File("F:\\word\\new_test1111.docx");
        FileOutputStream out = new FileOutputStream(new_file);
        out.write(byteArrayOutputStream.toByteArray());
//        XWPFDocument xwpfDocument = new XWPFDocument(new FileInputStream(path));
//        xwpfDocument.getDocument().getBody().
    }

    /**
     * 截取
     *
     * @throws IOException
     */
    private static void intercept() throws IOException {
        String pathname = "F:\\word\\i1.docx";
        File file = new File(pathname);
        XWPFDocument xdoc = new XWPFDocument(new FileInputStream(file));
        List<IBodyElement> bodyElements = xdoc.getBodyElements();
        int count = bodyElements.size();
        System.out.println(count);

        int start = 0;
        int end = count;
        for (int i = 0; i < count; i++) {
            IBodyElement bodyElement = bodyElements.get(i);
            BodyElementType bet = bodyElement.getElementType();
            if (bet == BodyElementType.PARAGRAPH) {
                // 段落
                XWPFParagraph paragraph = ((XWPFParagraph) bodyElement);
                // 判断该段落是否设置了大纲级别
                String control = getTitleLvl(xdoc, paragraph);
                if (paragraph.getText().contains("测试数据第二章") && control.equals("0")) {
                    start = i;
                }
                if (paragraph.getText().contains("测试数据第三章") && control.equals("0")) {
                    System.out.println(paragraph.getText());
                    System.out.println(i);
                    end = i - 1;
                }
            }
        }
        System.out.println("#################################################################");


        File file_new = new File(pathname);
        FileInputStream fis_new = new FileInputStream(file_new);
        XWPFDocument out_doc = new XWPFDocument(fis_new);

        System.out.println("start:" + start);
        System.out.println("end:" + end);
        for (int i = count - 1; i >= end; i--) {
            out_doc.removeBodyElement(i);
            // System.out.println(i + "==" + out_doc.removeBodyElement(i));
        }
        System.out.println("*****************");
        // 写入一个新文件
        for (int i = start - 1; i >= 0; i--) {
            out_doc.removeBodyElement(i);
            // System.out.println(i + "==" + out_doc.removeBodyElement(i));
        }

        File new_file = new File("new_test1111.docx");
        FileOutputStream out = new FileOutputStream(new_file);
        out_doc.write(out);
    }

    public static ByteArrayOutputStream interceptByInputStream(InputStream inputStream, String... titleName) throws IOException {
        MyXWPDocument xdoc = new MyXWPDocument(inputStream);
        List<IBodyElement> bodyElements = xdoc.getBodyElements();
        int count = bodyElements.size();
        System.out.println(count);

        int start = 0;
        int end = count;
        boolean isExit = false;
        for (int i = 0; i < count; i++) {
            IBodyElement bodyElement = bodyElements.get(i);
            BodyElementType bet = bodyElement.getElementType();
            if (bet == BodyElementType.PARAGRAPH) {
                // 段落
                XWPFParagraph paragraph = ((XWPFParagraph) bodyElement);
                // 判断该段落是否设置了大纲级别
                String control = getTitleLvl(xdoc, paragraph);
                if (control.equals("0") && !isExit) {
                    for (String s : titleName) {
                        if (paragraph.getText().contains(s)) {
                            start = i;
                            isExit = true;
                            break;
                        }
                    }
                    if (isExit) {
                        continue;
                    }
                }
                if (isExit && control.equals("0")) {
                    end = i - 1;
                    break;
                }
            }
        }
        log.info("#################################################################,开始分割开始标标记[{}],结束标记：[{}]", start, end);


        System.out.println("start:" + start);
        System.out.println("end:" + end);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        if (isExit) {
            for (int i = count; i >= end; i--) {
                xdoc.removeBodyElementAndSTD(i);
            }
            log.info("*****************");
            // 写入一个新文件
            for (int i = start - 1; i >= 0; i--) {
                xdoc.removeBodyElementAndSTD(i);
            }
            xdoc.write(stream);
        }

        return stream;
    }

    private static void testMerge() throws Exception {
        List<NiceXWPFDocument> list = new ArrayList<>();
        list.add(new CustomXWPFDocument(new FileInputStream("F://word/1-1.docx")));
        list.add(new CustomXWPFDocument(new FileInputStream("F://word/1-2.docx")));
        list.add(new CustomXWPFDocument(new FileInputStream("F://test/3.docx")));
        list.add(new CustomXWPFDocument(new FileInputStream("F://word/21.docx")));
        NiceXWPFDocument[] niceXWPFDocuments = new CustomXWPFDocument[list.size()];
        niceXWPFDocuments = list.toArray(niceXWPFDocuments);
        NiceXWPFDocument merge = merge(true, niceXWPFDocuments);
        String name = "F://word/t11.docx";
        FileOutputStream fileOutputStream = new FileOutputStream(name);
        merge.write(fileOutputStream);
    }

    public static void xmlPathToWord(String xmlPath, String outPath) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(xmlPath);
        byte[] bytes = fileInputStream.readAllBytes();
        String xml = new String(bytes, StandardCharsets.UTF_8);
        xmlToWord(xml, outPath);
    }

    public static void xmlToWord(String xml, String outPath) throws Exception {
        XWPFDocument xwpfDocument = new XWPFDocument();
        CTBody makeBody = CTBody.Factory.parse(xml);
        CTDocument1 document = xwpfDocument.getDocument();
        document.setBody(makeBody);
        xwpfDocument.write(new FileOutputStream(outPath));
    }

    public static String getWordXml(String path) throws Exception {
        NiceXWPFDocument e = new NiceXWPFDocument(new FileInputStream(path));
        return e.getDocument().getBody().toString();
    }

    public static void getWordXmlWrite(String path, String outPath) throws Exception {
        NiceXWPFDocument e = new NiceXWPFDocument(new FileInputStream(path));
        byte[] bytes = e.getDocument().getBody().toString().getBytes(StandardCharsets.UTF_8);
        FileOutputStream fileOutputStream = new FileOutputStream(outPath);
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }


    /**
     * word合并
     *
     * @param resourceFileList 需要合并的文件路径
     * @param targetPath       合并完成后的输出路径带文件名，（没有生成文件夹，如有需要自己改进）
     * @param removeResource   是否删除源文件
     */
    public static void mergeWord(List<File> resourceFileList, File targetPath, Boolean removeResource) {
        OutputStream dest = null;
        XWPFDocument doc = null;
        OPCPackage docOpen = null;
        FileInputStream docIn = null;

        try {
            dest = new FileOutputStream(targetPath);
            FileInputStream in = null;
            OPCPackage open = null;
            XWPFDocument document = null;
            for (int i = 0, resourceFileListSize = resourceFileList.size(); i < resourceFileListSize; i++) {
//                File file = new File(resourceFileList.get(i));
                try {
                    if (i == 0) {
                        docIn = new FileInputStream(resourceFileList.get(i));
                        docOpen = OPCPackage.open(docIn);
                        doc = new XWPFDocument(docOpen);
                    } else {
                        in = new FileInputStream(resourceFileList.get(i));
                        open = OPCPackage.open(in);
                        document = new XWPFDocument(open);
                        doc.createParagraph().setPageBreak(true);
                        appendBody(doc, document);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != in) {
                        in.close();
                    }
                    if (null != open) {
                        open.close();
                    }
                    if (null != document) {
                        document.close();
                    }
                }
            }
            doc.write(dest);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != dest) {
                    dest.close();
                }
                if (null != doc) {
                    doc.close();
                }
                if (null != docIn) {
                    docIn.close();
                }
                if (null != docOpen) {
                    docOpen.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (removeResource) {
//            for (String filePath : resourceFileList) {
//                new File(filePath).delete();
//            }
        }
    }

    /**
     * 合并模板
     *
     * @param niceXWPFDocuments 文本信息
     * @return 字节
     * @throws IOException io 异常
     */
    public static ByteArrayOutputStream mergeWord(NiceXWPFDocument[] niceXWPFDocuments) throws IOException {
        ByteArrayOutputStream dest = new ByteArrayOutputStream();
        XWPFDocument doc = null;

        XWPFDocument document = null;
        for (int i = 0, resourceFileListSize = niceXWPFDocuments.length; i < resourceFileListSize; i++) {
            try {
                if (i == 0) {
                    doc = niceXWPFDocuments[i];
                    XWPFParagraph paragraph = doc.createParagraph();
                    paragraph.setStyle("10");
                    doc.createParagraph().createRun().addBreak(BreakType.PAGE);
                } else {

                    document = niceXWPFDocuments[i];
//                    doc.createParagraph().setPageBreak(true);
                    if (i < resourceFileListSize - 1) {
                        doc.createParagraph().createRun().addBreak(BreakType.PAGE);
                    }
                    appendBody(doc, document);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        doc.write(dest);
        return dest;

    }

    public static void appendBody(XWPFDocument src, XWPFDocument append) throws Exception {
        CTBody src1Body = src.getDocument().getBody();
        CTBody src2Body = append.getDocument().getBody();

        List<XWPFPictureData> allPictures = append.getAllPictures();
        // 记录图片合并前及合并后的ID
        Map<String, String> map = new HashMap();
        for (XWPFPictureData picture : allPictures) {
            String before = append.getRelationId(picture);
            //将原文档中的图片加入到目标文档中
            String after = src.addPictureData(picture.getData(), Document.PICTURE_TYPE_JPEG);
            map.put(before, after);
        }
        appendBody(src1Body, src2Body, map);
    }

    private static void appendBody(CTBody src, CTBody append, Map<String, String> map) throws Exception {
//        XmlOptions optionsOuter = new XmlOptions();
//        optionsOuter.setSaveOuter();
        String appendString = append.xmlText();

        String srcString = src.xmlText();
        String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
        String mainPart = srcString.substring(srcString.indexOf(">") + 1, srcString.lastIndexOf("<")).replace("<w:p/>", "");
        String sufix = srcString.substring(srcString.lastIndexOf("<"));
        String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<")).replace("<w:p/>", "");

        if (map != null && !map.isEmpty()) {
            ListIterator<Map.Entry<String, String>> i = new ArrayList<>(map.entrySet()).listIterator(map.size());
            LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>();
            while (i.hasPrevious()) {
                Map.Entry<String, String> entry = i.previous();
                linkedHashMap.put(entry.getKey(), entry.getValue());
            }
            Iterator it1 = linkedHashMap.entrySet().iterator();
            while (it1.hasNext()) {
                Map.Entry<String, String> entry = (Map.Entry<String, String>) it1.next();
                addPart = addPart.replace("<a:blip r:embed=\"" + entry.getKey(), "<a:blip r:embed=\"" + entry.getValue());
            }
        }
        //将两个文档的xml内容进行拼接
        CTBody makeBody = CTBody.Factory.parse(prefix + mainPart + addPart + sufix);
        src.set(makeBody);
    }


    public static JSONArray getTreeToJSONArray(List<WordTitle> allTitle) {
        //返回数据结构
        List<WordTitle> list = getTree(allTitle);

        return JSONArray.parseArray(JSONArray.toJSONString(list));
    }

    public static String getTreeToJSONString(List<WordTitle> allTitle) {
        //返回数据结构
        List<WordTitle> list = getTree(allTitle);

        return JSONArray.toJSONString(list);
    }

    private static List<WordTitle> getTree(List<WordTitle> allTitle) {
        List<WordTitle> list = new ArrayList<>();
        for (int i = 0; i < allTitle.size(); i++) {
            if (i == 0) {
                list.add(allTitle.get(i));
                continue;
            }
            if (allTitle.get(i - 1).getStyle() < allTitle.get(i).getStyle()) {
                allTitle.get(i - 1).add(allTitle.get(i));
                allTitle.get(i).setParent(allTitle.get(i - 1));
            } else {
                WordTitle wordTitle1 = allTitle.get(i);
                if (Objects.equals(allTitle.get(i - 1).getStyle(), wordTitle1.getStyle())) {
                    WordTitle parent = allTitle.get(i - 1).getParent();
                    if (parent == null) {
                        list.add(wordTitle1);
                    } else {
                        parent.add(wordTitle1);
                        wordTitle1.setParent(parent);
                    }
                } else {
                    // 往父辈上面查看看 有父辈
                    WordTitle wordTitle = allTitle.get(i);
                    WordTitle parent = allTitle.get(i - 1).getParent();

                    while (true) {
                        if (parent == null) {
                            list.add(allTitle.get(i));
                            break;
                        }
                        if (parent.getStyle() < wordTitle.getStyle()) {
                            parent.add(wordTitle);
                            wordTitle.setParent(parent);
                            break;
                        }
                        parent = parent.parent;
                    }
                }
            }
        }
        return list;
    }

    public static void toString1(JSONArray wordTitle, String title) {
        for (Object object : wordTitle) {
            JSONObject object1 = (JSONObject) object;
            JSONArray child = (JSONArray) object1.get("child");
            if (child == null) {
                continue;
            }
            toString1(child, title + "-");
        }
    }


    /**
     * 合并模板
     *
     * @param files 文件信息
     * @param out   返回数据
     * @throws Exception
     */
    public static void mergeDocAndPage(List<File> files, File out) throws Exception {
        NiceXWPFDocument[] niceXWPFDocument = new NiceXWPFDocument[files.size()];
        File current = null;
        for (int i = 0; i < files.size(); i++) {
            current = files.get(i);
            niceXWPFDocument[i] = new NiceXWPFDocument(new FileInputStream(files.get(i)));
        }
        try (NiceXWPFDocument merge = merge(true, niceXWPFDocument);
             FileOutputStream outputStream = new FileOutputStream(out);
        ) {
            merge.write(outputStream);
            log.info("word本地文件，模板合并成功");
        } catch (Exception e) {
            for (File file : files) {
                log.error(file.getAbsolutePath());
            }
            e.printStackTrace();
            log.error("错误文件" + current);
            log.error("word 本地文件，模板合并异常，异常原因：{}，异常信息：{},", e.getCause(), e.getMessage());
        }
    }

    /**
     * 合并文件
     *
     * @param files 字节数组
     * @return 返回数据
     * @throws Exception IO异常
     */
    public static ByteArrayOutputStream mergeDocAndPageByBytes(List<byte[]> files) throws Exception {
        NiceXWPFDocument[] niceXWPFDocument = new NiceXWPFDocument[files.size()];
        for (int i = 0; i < files.size(); i++) {
            niceXWPFDocument[i] = new NiceXWPFDocument(new ByteArrayInputStream(files.get(i)));
        }
        return getByteArrayOutputStream(niceXWPFDocument);
    }

    /**
     * 合并文件
     *
     * @param files 输入流集合
     * @return 返回数据
     * @throws Exception IO异常
     */
    public static ByteArrayOutputStream mergeDocAndPageByInputStream(List<InputStream> files) throws Exception {
        NiceXWPFDocument[] niceXWPFDocument = new NiceXWPFDocument[files.size()];
        for (int i = 0; i < files.size(); i++) {
            niceXWPFDocument[i] = new CustomXWPFDocument(files.get(i));
        }
        return getByteArrayOutputStream(niceXWPFDocument);
    }

    /**
     * 合并文件
     *
     * @param niceXWPFDocument 文档集合
     * @return 返回数据
     */
    private static ByteArrayOutputStream getByteArrayOutputStream(NiceXWPFDocument[] niceXWPFDocument) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            merge(true, niceXWPFDocument).write(out);
            return out;
        } catch (Exception e) {
            log.error("模板合并错误", e);
        } finally {
            close(niceXWPFDocument);
        }
        return null;
    }

    private static void close(NiceXWPFDocument[] niceXWPFDocument) {
        for (NiceXWPFDocument xwpfDocument : niceXWPFDocument) {
            try {
                xwpfDocument.close();
            } catch (IOException e) {
                log.error("关闭失败");
            }
        }
    }


    /**
     * @param page             是否分页
     * @param niceXWPFDocument 已合并word文档
     * @return 合并完成的word文档
     * @throws Exception IO异常
     */
    public static NiceXWPFDocument merge(Boolean page, NiceXWPFDocument... niceXWPFDocument) throws Exception {
        NiceXWPFDocument newDoc = null;
        for (NiceXWPFDocument xwpfDocument : niceXWPFDocument) {
            if (page) {
                //设置分页符和下一页脱离关联

//                newDoc.createParagraph().createRun().addBreak(BreakType.PAGE);
                // 首先获取 document 的 Section 信息
                CTBody body = xwpfDocument.getDocument().getBody();
                // 放心, 这个 body 肯定不为空, 否则这个文档就有问题了
                XWPFParagraph paragraph = xwpfDocument.createParagraph();
                // 新创建的段落肯定没有 PPr, 所以需要新创建一个
                CTPPr ctpPr = paragraph.getCTP().addNewPPr();
                // 这一句其实就是设置下一页的分页符了
                CTSectPr sectPr = ctpPr.addNewSectPr();
                // 把文档上的 sectPr 中的某些属性赋值给新创建的段落的 sectPr
//                CTBody body1 = xwpfDocument.getDocument().getBody();

                CTSectPr bodySectPr = body.getSectPr();
                //设置文件大小CTSectPr
                sectPr.setPgSz(bodySectPr.getPgSz());
                //设置页边距
                CTPageMar pgMar = bodySectPr.getPgMar();
                if (pgMar != null) {
                    sectPr.setPgMar(pgMar);
                }
                sectPr.setCols(bodySectPr.getCols());
                sectPr.setDocGrid(bodySectPr.getDocGrid());

            }
            if (newDoc == null) {
                newDoc = xwpfDocument;
                continue;
            }
            newDoc = newDoc.merge(xwpfDocument);
        }
        return newDoc;
    }


    /**
     * 获取文件标题
     *
     * @param path 本地文件路径
     * @return 所有标题
     * @throws Exception IO异常
     */
    public static List<String> getTitle(String path) throws Exception {
        return getTitle(new FileInputStream(path), null);
    }

    /**
     * 获取文件标题
     *
     * @param in          输入留
     * @param sourceStyle 标题格式
     * @return 返回标题
     * @throws Exception IO异常
     */
    public static List<String> getTitle(InputStream in, String sourceStyle) throws Exception {
        List<String> titles = new ArrayList<>();
        try (
                //加载Word测试文档
                InputStream is = in;
                XWPFDocument doc = new XWPFDocument(is)
        ) {
            //将得到包含段落列表
            List<XWPFParagraph> paras = doc.getParagraphs();
            //获取标题
            for (XWPFParagraph xwpfParagraph : paras) {
                String style = xwpfParagraph.getStyle();
                if (style == null) {
                    continue;
                }
                if (sourceStyle == null) {
                    if (xwpfParagraph.getStyle().matches("TOC\\w+")) {
                        titles.add(xwpfParagraph.getText());
                    }
                } else if (sourceStyle.equals(style)) {
                    titles.add(xwpfParagraph.getText());
                }

            }
        }
        return titles;
    }

    public static List<WordTitle> getAllTitle(InputStream in) throws Exception {
        List<WordTitle> titles = new ArrayList<>();
        try (
                //加载Word测试文档
                InputStream is = in;
                XWPFDocument doc = new XWPFDocument(is)
        ) {
            //将得到包含段落列表
            List<XWPFParagraph> paras = doc.getParagraphs();
            //获取标题
            for (XWPFParagraph xwpfParagraph : paras) {
                String titleLvl = getTitleLvl(doc, xwpfParagraph);
                if (titleLvl.isEmpty()) {
                    continue;
                }
                if (xwpfParagraph.getText().isEmpty()) {
                    continue;
                }
                WordTitle wordTitle = new WordTitle();
                wordTitle.setTitle(xwpfParagraph.getText());
                wordTitle.setStyle(Integer.valueOf(titleLvl));
                wordTitle.setId(IdUtil.getSnowflakeNextIdStr() + "");
                titles.add(wordTitle);
//                String style = xwpfParagraph.getStyle();
//                if (style != null && pattern.matcher(style).matches()) {
//                    WordTitle wordTitle = new WordTitle();
//                    wordTitle.setTitle(xwpfParagraph.getText());
//                    wordTitle.setStyle(Integer.valueOf(style));
//                    titles.add(wordTitle);
//                }


            }
        }
        return titles;
    }

    private static String getTitleLvl(XWPFDocument doc, XWPFParagraph para) {
        String titleLvl = "";
        try {
            //判断该段落是否设置了大纲级别
            if (para.getCTP().getPPr().getOutlineLvl() != null) {
                BigInteger val = para.getCTP().getPPr().getOutlineLvl().getVal();
                if (val == null) {
                    return titleLvl;
                }
                return val.compareTo(new BigInteger("8")) > 0 ? titleLvl : String.valueOf(val);
            }
        } catch (Exception e) {
        }
        try {
            //判断该段落的样式是否设置了大纲级别
            if (doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl() != null) {

                return String.valueOf(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getPPr().getOutlineLvl().getVal());
            }
        } catch (Exception e) {
        }
        try {
            //判断该段落的样式的基础样式是否设置了大纲级别
            if (doc.getStyles().getStyle(doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal())
                    .getCTStyle().getPPr().getOutlineLvl() != null) {
                String styleName = doc.getStyles().getStyle(para.getStyle()).getCTStyle().getBasedOn().getVal();
                return String.valueOf(doc.getStyles().getStyle(styleName).getCTStyle().getPPr().getOutlineLvl().getVal());
            }
        } catch (Exception e) {

        }

        return titleLvl;
    }

    /**
     * 获取标题
     *
     * @param bytes 文件字节
     * @return 标题
     * @throws Exception IO异常
     */
    public static List<String> getTitle(byte[] bytes) throws Exception {
        return getTitle(new ByteArrayInputStream(bytes), null);
    }

    @Data
    public static class WordTitle {
        //        @JSONField(serialize = false)
        Integer style;
        //        @JSONField(serialize = false)
        String title;

        String id;
        List<WordTitle> child;

        @JSONField(serialize = false)
        WordTitle parent;

        public void add(WordTitle wordTitle) {
            if (child == null) {
                child = new ArrayList<>();
            }
            child.add(wordTitle);
        }
    }
}